import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import os
import tinify
import threading
import queue
from PIL import Image

# 定义支持的图片文件扩展名
SUPPORTED_FORMATS = ('.png', '.jpg', '.jpeg', '.webp')
CONFIG_FILE = "tinypng_api_key.txt"

class TinyPNGCompressorApp:
    def __init__(self, master):
        self.master = master
        self.master.title("TinyPNG 批量压缩工具")
        self.master.geometry("650x550")
        self.master.resizable(False, False)

        # 创建一个消息队列用于线程间通信
        self.log_queue = queue.Queue()

        self.create_widgets()
        self.load_api_key()
        
        # 启动队列处理器，用于更新UI
        self.master.after(100, self.process_queue)

    def create_widgets(self):
        # --- 主框架 ---
        main_frame = ttk.Frame(self.master, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)

        # --- API Key 设置 ---
        api_frame = ttk.LabelFrame(main_frame, text="API Key 设置")
        api_frame.pack(fill=tk.X, padx=5, pady=5)
        
        ttk.Label(api_frame, text="TinyPNG API Key:").pack(side=tk.LEFT, padx=5, pady=5)
        self.api_key_var = tk.StringVar()
        self.api_key_entry = ttk.Entry(api_frame, textvariable=self.api_key_var, width=40, show="*")
        self.api_key_entry.pack(side=tk.LEFT, padx=5, pady=5, expand=True, fill=tk.X)
        
        self.save_key_var = tk.BooleanVar(value=True)
        self.save_key_check = ttk.Checkbutton(api_frame, text="保存Key", variable=self.save_key_var)
        self.save_key_check.pack(side=tk.LEFT, padx=5, pady=5)

        # --- 文件夹选择 ---
        folder_frame = ttk.LabelFrame(main_frame, text="目标文件夹")
        folder_frame.pack(fill=tk.X, padx=5, pady=5)

        self.folder_path_var = tk.StringVar()
        self.folder_entry = ttk.Entry(folder_frame, textvariable=self.folder_path_var, state='readonly')
        self.folder_entry.pack(side=tk.LEFT, padx=5, pady=5, expand=True, fill=tk.X)
        self.browse_button = ttk.Button(folder_frame, text="浏览...", command=self.select_folder)
        self.browse_button.pack(side=tk.LEFT, padx=5, pady=5)

        # --- 压缩设置 ---
        settings_frame = ttk.LabelFrame(main_frame, text="压缩设置 (可选)")
        settings_frame.pack(fill=tk.X, padx=5, pady=5)

        self.resize_method_var = tk.StringVar(value="none")
        ttk.Radiobutton(settings_frame, text="不调整尺寸", variable=self.resize_method_var, value="none").pack(anchor=tk.W, padx=10)
        
        width_frame = ttk.Frame(settings_frame)
        width_frame.pack(fill=tk.X, padx=10)
        ttk.Radiobutton(width_frame, text="调整宽度为:", variable=self.resize_method_var, value="width").pack(side=tk.LEFT)
        self.resize_width_var = tk.StringVar(value="800")
        self.width_entry = ttk.Entry(width_frame, textvariable=self.resize_width_var, width=8)
        self.width_entry.pack(side=tk.LEFT, padx=5)
        ttk.Label(width_frame, text="像素 (等比例缩放)").pack(side=tk.LEFT)

        height_frame = ttk.Frame(settings_frame)
        height_frame.pack(fill=tk.X, padx=10)
        ttk.Radiobutton(height_frame, text="调整高度为:", variable=self.resize_method_var, value="height").pack(side=tk.LEFT)
        self.resize_height_var = tk.StringVar(value="600")
        self.height_entry = ttk.Entry(height_frame, textvariable=self.resize_height_var, width=8)
        self.height_entry.pack(side=tk.LEFT, padx=5)
        ttk.Label(height_frame, text="像素 (等比例缩放)").pack(side=tk.LEFT)

        # --- 执行与进度 ---
        action_frame = ttk.Frame(main_frame)
        action_frame.pack(fill=tk.X, padx=5, pady=10)

        self.start_button = ttk.Button(action_frame, text="开始压缩", command=self.start_compression)
        self.start_button.pack(side=tk.LEFT, padx=5, pady=5)

        self.progress_bar = ttk.Progressbar(action_frame, orient='horizontal', mode='determinate')
        self.progress_bar.pack(side=tk.LEFT, padx=5, pady=5, expand=True, fill=tk.X)

        # --- 日志输出 ---
        log_frame = ttk.LabelFrame(main_frame, text="日志")
        log_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        self.log_text = scrolledtext.ScrolledText(log_frame, wrap=tk.WORD, height=10, state='disabled')
        self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

    def select_folder(self):
        folder_selected = filedialog.askdirectory()
        if folder_selected:
            self.folder_path_var.set(folder_selected)
            self.log_message(f"已选择文件夹: {folder_selected}")

    def log_message(self, message):
        # 线程安全地更新日志文本框
        self.log_queue.put(message)

    def process_queue(self):
        # 从队列中获取消息并更新UI
        try:
            while True:
                message = self.log_queue.get_nowait()
                self.log_text.config(state='normal')
                self.log_text.insert(tk.END, message + "\n")
                self.log_text.see(tk.END) # 滚动到底部
                self.log_text.config(state='disabled')
        except queue.Empty:
            pass
        finally:
            self.master.after(100, self.process_queue) # 100毫秒后再次检查

    def start_compression(self):
        api_key = self.api_key_var.get().strip()
        folder_path = self.folder_path_var.get()
        resize_method = self.resize_method_var.get()

        if not api_key:
            messagebox.showerror("错误", "请输入您的 TinyPNG API Key！")
            return
        if not folder_path:
            messagebox.showerror("错误", "请选择一个要处理的文件夹！")
            return

        if self.save_key_var.get():
            self.save_api_key(api_key)

        # 禁用按钮，防止重复点击
        self.start_button.config(state='disabled')
        self.browse_button.config(state='disabled')
        self.log_text.config(state='normal')
        self.log_text.delete(1.0, tk.END) # 清空日志
        self.log_text.config(state='disabled')
        
        self.log_message("正在准备压缩...")

        # 在新线程中执行压缩任务
        compression_thread = threading.Thread(
            target=self.compression_worker,
            args=(api_key, folder_path, resize_method),
            daemon=True
        )
        compression_thread.start()

    def compression_worker(self, api_key, folder_path, resize_method):
        try:
            tinify.key = api_key
            tinify.validate()
            self.log_message("API Key 验证成功！")
        except tinify.Error as e:
            self.log_message(f"API Key 验证失败: {e.message}")
            self.restore_ui_state()
            return
        
        image_files = []
        for root, _, files in os.walk(folder_path):
            for file in files:
                if file.lower().endswith(SUPPORTED_FORMATS):
                    image_files.append(os.path.join(root, file))

        if not image_files:
            self.log_message("未在文件夹中找到支持的图片格式 (png, jpg, jpeg, webp)。")
            self.restore_ui_state()
            return

        self.log_message(f"找到 {len(image_files)} 张图片，开始处理...")
        self.progress_bar['maximum'] = len(image_files)
        
        total_before_size = 0
        total_after_size = 0
        processed_count = 0

        for i, file_path in enumerate(image_files):
            try:
                self.log_message(f"\n[{i+1}/{len(image_files)}] 正在处理: {os.path.basename(file_path)}")
                
                original_size = os.path.getsize(file_path)
                total_before_size += original_size

                source = tinify.from_file(file_path)

                # --- 调整尺寸逻辑 ---
                if resize_method != "none":
                    with Image.open(file_path) as img:
                        w, h = img.size
                    
                    if resize_method == "width":
                        new_width = int(self.resize_width_var.get())
                        if w > new_width:
                            self.log_message(f"  -> 调整宽度从 {w}px 到 {new_width}px...")
                            source = source.resize(method="scale", width=new_width)
                        else:
                             self.log_message(f"  -> 宽度 {w}px 小于目标 {new_width}px，跳过调整。")
                    elif resize_method == "height":
                        new_height = int(self.resize_height_var.get())
                        if h > new_height:
                            self.log_message(f"  -> 调整高度从 {h}px 到 {new_height}px...")
                            source = source.resize(method="scale", height=new_height)
                        else:
                             self.log_message(f"  -> 高度 {h}px 小于目标 {new_height}px，跳过调整。")

                source.to_file(file_path) # 直接替换原文件
                
                new_size = os.path.getsize(file_path)
                total_after_size += new_size
                processed_count += 1
                
                savings = original_size - new_size
                savings_percent = (savings / original_size * 100) if original_size > 0 else 0
                
                self.log_message(f"  -> 成功! 大小: {self.format_bytes(original_size)} -> {self.format_bytes(new_size)}. 节省了 {self.format_bytes(savings)} ({savings_percent:.1f}%)")

            except tinify.Error as e:
                self.log_message(f"  -> 压缩失败: {e.message} - {file_path}")
                total_after_size += os.path.getsize(file_path) # 失败则按原大小计算
            except Exception as e:
                self.log_message(f"  -> 发生未知错误: {e} - {file_path}")
                total_after_size += os.path.getsize(file_path)
            
            self.progress_bar['value'] = i + 1

        self.log_message("\n---------- 操作完成 ----------")
        total_savings = total_before_size - total_after_size
        total_savings_percent = (total_savings / total_before_size * 100) if total_before_size > 0 else 0
        self.log_message(f"总共处理: {processed_count} / {len(image_files)} 张图片")
        self.log_message(f"原始总大小: {self.format_bytes(total_before_size)}")
        self.log_message(f"压缩后总大小: {self.format_bytes(total_after_size)}")
        self.log_message(f"总共节省空间: {self.format_bytes(total_savings)} ({total_savings_percent:.1f}%)")
        self.log_message("-----------------------------")

        self.restore_ui_state()

    def restore_ui_state(self):
        self.start_button.config(state='normal')
        self.browse_button.config(state='normal')

    def save_api_key(self, api_key):
        try:
            with open(CONFIG_FILE, "w") as f:
                f.write(api_key)
        except Exception as e:
            self.log_message(f"保存API Key失败: {e}")

    def load_api_key(self):
        if os.path.exists(CONFIG_FILE):
            try:
                with open(CONFIG_FILE, "r") as f:
                    api_key = f.read().strip()
                    if api_key:
                        self.api_key_var.set(api_key)
                        self.log_message("已自动加载保存的API Key。")
            except Exception as e:
                self.log_message(f"加载API Key失败: {e}")

    def format_bytes(self, size):
        # 格式化文件大小，使其更易读
        power = 1024
        n = 0
        power_labels = {0: '', 1: 'KB', 2: 'MB', 3: 'GB', 4: 'TB'}
        while size > power and n < len(power_labels):
            size /= power
            n += 1
        return f"{size:.2f} {power_labels[n]}"


if __name__ == "__main__":
    root = tk.Tk()
    app = TinyPNGCompressorApp(root)
    root.mainloop()